package Script;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import Calculate.ByteCalc;
import Error.StandardError;

import interfaces.ErrorReport;
import interfaces.Pointer;
import interfaces.PointerFactory;
import interfaces.PointerReader;
import interfaces.ErrorReport.ErrorType;

/**
 * Reads pointers from a game file based on a set of offsets.
 * 
 * @author Tobias
 */
public class JPointerReader implements PointerReader {
	File 				gamefile;
	ArrayList<Offset> 	offsets;
	PointerFactory 		factory;
	
	/**
	 * Initialize the factory parameters.
	 * 
	 * @param game game file to read pointers from.
	 * @param offsets List of all offsets to read from.
	 * @param factory
	 */
	@SuppressWarnings("unchecked")
	public JPointerReader(File game, ArrayList<Offset> offsets, 
													PointerFactory factory) {
		this.gamefile = game;
		this.offsets = offsets;
		this.factory = factory;
		
		Collections.sort(offsets);
	}
	
	@Override
	public HashMap<Integer, Pointer> getPointers() throws IOException {
		RandomAccessFile 			in = new RandomAccessFile(gamefile, "r");
		int 						pointerSize = factory.pointerSize();
		byte[] 						pointerBuf;
		HashMap<Integer, Pointer> 	pList = new HashMap<Integer, Pointer>();
		int							i = 1; // Iterator
		Offset						put;
		
		/*
		 * For each offset in the offset collection, skip a number
		 * of bytes from the current stream position to reach
		 * the offset. Then, read a number of bytes (determined by the factory)
		 * and repeat.
		 */
		for (Offset o : offsets) {
			pointerBuf = new byte[pointerSize];
			
			/* Jump to the current offset and read the pointe. */
			in.seek(o.getValue());
			in.read(pointerBuf);
			put = new Offset(ByteCalc.byteArrayToLong(pointerBuf));
			
			pList.put(i, factory.newPointer(i, put,	o));
			i++;
		}
		
		in.close();
		return pList;
	}

	@Override
	public PointerFactory getFactory() {
		return factory;
	}

	@Override
	public ArrayList<ErrorReport> isValid() {
		Offset current;
		Offset next;
		int pSize = factory.pointerSize();
		ArrayList<ErrorReport> err = new ArrayList<ErrorReport>();
		RandomAccessFile file = null;
		long fileLength;

		/* 
		 * Check if two pointers aren't sorted by size, or
		 * if they are but still overlaps. 
		 */
		for (int i = 0; i < offsets.size() - 1; i++) {
			current = offsets.get(i);
			next = offsets.get(i + 1);
			
			if (next.getValue() < current.getValue()) {
				err.add(new StandardError("Pointers unsorted",
										  ErrorType.ERROR));
				
			} else if (next.getValue() < (current.getValue() + pSize)) {
				err.add(new StandardError("Pointer offsets " + 
								current.getHex(pSize) + " and " + next.getHex(pSize) + 
								" overlap with eachother.",
								ErrorType.ERROR));
			}
		}
		
		/* 
		 * Check if the last (and largest) offset points beyond
		 * the filesize.
		 */
		try {
			file = new RandomAccessFile(gamefile, "r");
			fileLength = file.length();
			
			if (offsets.size() > 0 
				&& offsets.get(offsets.size() - 1).getValue() > fileLength) {
				err.add(new StandardError("Given offset " + 
									offsets.get(offsets.size() - 1).getHex(pSize) + 
									" points outside file size.",
									ErrorType.ERROR));
			}
			
		} catch (FileNotFoundException e) {
			err.add(new StandardError("error reading game file",
														ErrorType.ERROR));
			e.printStackTrace();
		} catch (IOException e) {
			err.add(new StandardError("error reading game file",
					ErrorType.ERROR));
			e.printStackTrace();
			
		} finally {
			
			/* Free up file resources. */
			if (file != null) {
				try {
					file.close();
				} catch (IOException e) {
				}
			}
		}
		
		return err;
	}

}
